﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using NAudio;
using NAudio.Wave;

using System.Threading;

namespace GA_Chiptunes
{
    [Serializable]
    class Organism
    {
        WaveTone tone = new WaveTone(100, 0.1);
        DirectSoundOut output = new DirectSoundOut();
        BlockAlignReductionStream stream;

        public List<List<double[]>> Genotype = new List<List<double[]>>() { };

        static Random number = new Random();
        public int mutationRate = 16;

        public int maxFrequency;
        public int maxAmplitude;
        public int maxDuration;

        public int length;

        public Organism(int chromosomeCount, int genesPerChromosome)
        {
            Genotype = NewOrganism(chromosomeCount, genesPerChromosome);
        }

        public Organism(int chrom, int gene, int freq, int amp, int time)
        {
            maxFrequency = freq;
            maxAmplitude = amp;
            maxDuration = time;
            Genotype = NewOrganism(chrom, gene);
        }

        public Organism()
        {
            Genotype = new List<List<double[]>>() { };
            length = Genotype.Count;
        }

        public Organism(List<List<double[]>> genotype)
        {
            Genotype = genotype;
            length = Genotype.Count;
        }

        public Organism(Organism other)
        {
            Genotype = other.Genotype;
            length = Genotype.Count;
        }

        public void DisplayParameters()
        {
            Console.WriteLine(maxFrequency);
            Console.WriteLine(maxAmplitude);
            Console.WriteLine(maxDuration);
            Console.WriteLine(mutationRate + "\n");
        }

        public List<double[]> ChromosomeSelector()
        {
            try
            {
                int position = number.Next(0, Genotype.Count - 1);
                return Genotype[position];
            }
            catch (ArgumentOutOfRangeException err)
            {
                return Genotype[0];
            }
        }

        public double[] NewGene()
        {
            double[] gene = new double[3];
            gene[0] = number.Next(0, maxFrequency);
            gene[1] = (double)number.Next(0, maxAmplitude) / 1000;
            gene[2] = (double)number.Next(0, maxDuration);
            return gene;
        }

        public List<double[]> NewChromosome(int geneCount)
        {
            List<double[]> chromo = new List<double[]>() { };
            for (int i = 0; i < geneCount; i++)
            {
                InsertGene(chromo);
            }
            length = Genotype.Count;
            return chromo;
        }

        public List<List<double[]>> NewOrganism(int chromoCount, int geneCount)
        {
            List<List<double[]>> organism = new List<List<double[]>>() { };
            for (int i = 0; i < chromoCount; i++)
            {
                organism.Insert(0, NewChromosome(geneCount));
            }
            return organism;
        }

        public void DisplayGene(double[] gene)
        {
            for (int i = 0; i < gene.Length; i++)
            {
                Console.Write(gene[i] + " ");
            }
            Console.Write("\n");
        }

        public void DisplayChromosome(List<double[]> chromo)
        {
            for (int i = 0; i < chromo.Count; i++)
            {
                DisplayGene(chromo[i]);
            }
            Console.Write("\n");
        }

        public void DisplayOrganism()
        {
            Console.WriteLine("-------- Organism --------");
            for (int i = 0; i < Genotype.Count; i++)
            {
                Console.WriteLine("       chromosome " + i + "       ");
                DisplayChromosome(Genotype[i]);
                Console.Write("\n");
            }
        }

        public void PlayGene(double[] gene, DirectSoundOut tempOutput)
        {
            try
            {
                tone.frequency = gene[0];
                tone.amplitude = gene[1];

                stream = new BlockAlignReductionStream(tone);
                tempOutput.Init(stream);
                tempOutput.Play();
            }
            catch { }
        }

        public void PlayChromosome(List<double[]> chromo)
        {
            try
            {
                DirectSoundOut tempOutput = new DirectSoundOut();

                for (int i = 0; i < chromo.Count; i++)
                {
                    PlayGene(chromo[i], tempOutput);
                    System.Threading.Thread.Sleep((int)chromo[i][2]);
                }
                tempOutput.Stop();
            }
            catch { }
        }

        public void PlayOrganism()
        {
            try
            {
                for (int i = 0; i < Genotype.Count; i++)
                {
                    PlayChromosome(Genotype[i]);
                    System.Threading.Thread.Sleep(50);
                }
            }
            catch { }
        }

        public double[] PointMutation(double[] gene)
        {
            try
            {
                int temp = number.Next(0, 3);
                double[] newGene = new double[3];
                Array.Copy(gene, newGene, 3);

                // Frequency.
                if (temp == 0)
                {
                    newGene[0] = number.Next(1, maxFrequency);
                }

                // Amplitude.  Maximum is 1.0.
                if (temp == 1)
                {
                    newGene[1] = (double)number.Next(1, maxAmplitude) / 1000;
                }

                // Duration.  Maximum is 4 seconds;
                if (temp == 2)
                {
                    newGene[2] = (double)number.Next(1, maxDuration);
                }

                return newGene;
            }
            catch (ArgumentOutOfRangeException err)
            {
                return gene;
            }
        }

        public void InsertGene(List<double[]> chromo)
        {
            int position = number.Next(0, chromo.Count);
            chromo.Insert(position, NewGene());
        }

        public void InsertChromosome(List<double[]> chromo)
        {
            int position = number.Next(0, length);
            Genotype.Insert(position, chromo);
            length = Genotype.Count;
        }

        public void DeleteGene()
        {
            List<double[]> chromo = ChromosomeSelector();
            try
            {
                int position = number.Next(0, chromo.Count);
                chromo.RemoveAt(position);
            }
            catch (ArgumentOutOfRangeException err)
            {
                return;
            }
        }

        public void DeleteChromosome()
        {
            try
            {
                int selection = number.Next(0, Genotype.Count);
                Genotype.RemoveAt(selection);
                length = Genotype.Count;
            }
            catch (ArgumentOutOfRangeException err)
            {
                return;
            }
        }

        public void DuplicateGene()
        {
            List<double[]> chromo = ChromosomeSelector();
            try
            {
                int position = number.Next(0, chromo.Count);
                double[] duplicate = chromo[position];
                chromo.Insert(position, duplicate);
            }
            catch (ArgumentOutOfRangeException err)
            {
                return;
            }
        }

        public void DuplicateChromosome()
        {
            try
            {
                int selection = number.Next(0, Genotype.Count);
                Genotype.Insert(selection, Genotype[selection]);
                length = Genotype.Count;
            }
            catch (ArgumentOutOfRangeException err)
            {
                return;
            }
        }

        public void Mutate()
        {
            int chromCount = Genotype.Count;
            int decision;
            int chromPos;

            if (chromCount == 0)
            {
                return;
            }

            for (int i = 0; i < mutationRate; i++)
            {
                decision = number.Next(0, 8);
                chromPos = number.Next(0, chromCount - 1);
                switch (decision)
                {
                    case 1:
                        for (int j = 0; j < 3; j++)
                        {
                            try
                            {
                                int geneCount = Genotype[chromPos].Count;
                                PointMutation(Genotype[chromPos][geneCount]);
                                chromPos = number.Next(0, chromCount);
                            }
                            catch (ArgumentOutOfRangeException err)
                            {
                                break;
                            }
                        }
                        break;
                    case 2:
                        try
                        {
                            InsertGene(Genotype[chromPos]);
                            break;
                        }
                        catch (ArgumentOutOfRangeException err)
                        {
                            break;
                        }
                    case 3:
                        DuplicateGene();
                        break;
                    case 4:
                        DeleteGene();
                        break;
                    case 5:
                        NewChromosome(4);
                        break;
                    case 6:
                        DeleteChromosome();
                        break;
                    case 7:
                        DuplicateChromosome();
                        break;
                }
            }
        }
    }
}
